package com.xiashitech.agent.instrumentation.db.statement.sanitizer.impl;

// import cn.hutool.core.collection.CollUtil;
import com.xiashitech.agent.proxy.hutool.CollUtil;

import com.xiashitech.agent.cache.authquery.extloadproxy.SqlParserLFUCacheProxy;
import com.xiashitech.agent.cache.authquery.extloadproxy.AuthFactResponseLFUCacheProxy;
import com.xiashitech.agent.config.StartConfig;
import com.xiashitech.agent.constants.ErrorCode;
import com.xiashitech.agent.constants.XSConstants;
import com.xiashitech.agent.db.entity.DesensitiseRule;
import com.xiashitech.agent.db.entity.UserPermission;
import com.xiashitech.agent.proxy.hutool.HashUtil;
import com.xiashitech.agent.proxy.hutool.StrUtil;
import com.xiashitech.agent.service.extloadproxy.AuthQueryServiceProxy;
import com.xiashitech.agent.utils.JsonUtil;
import com.xiashitech.agent.utils.XSUtil;
import com.xiashitech.interfaces.apiservice.UserTracingMsg;
import com.xiashitech.interfaces.apiservice.dto.AuthFactRequest;
import com.xiashitech.interfaces.apiservice.dto.AuthFactResponse;
import com.xiashitech.interfaces.apiservice.dto.base.*;
import com.xiashitech.interfaces.sqlparser.SqlParser;
import com.xiashitech.interfaces.sqlparser.dto.ResultDTO;
import com.xiashitech.sqlparser.sqlvisitor.finder.TableAndColumnFinder;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class XSSqlLocalSanitizer implements UserTracingMsg {

    private static final XSSqlLocalSanitizer instance = new XSSqlLocalSanitizer();
    public static XSSqlLocalSanitizer getInstance() {
        return instance;
    }
    private XSSqlLocalSanitizer() {}

    // 压测调优：在这里再做一个二级缓存
    // 为什么不去UserTracingService做二级缓存？那里提取缓存Key比较麻烦，且实测那里的代码对QPS没有明显影响。
    @Override
    public AuthFactResponse supplementUserTracing(AuthFactRequest authFactRequest) {
        AuthFactResponse authFactResponse = null;
        String cacheKey = String.valueOf(HashUtil.apHash(StrUtil.format("{}:{}:{}", authFactRequest.getSystemId(), authFactRequest.getUserId(),
                authFactRequest.getDb() == null ? "" : authFactRequest.getDb().getSql())));
        if (AuthFactResponseLFUCacheProxy.hasKey(cacheKey)){
            XSUtil.debug("缓存命中 AuthFactResponseLFUCache，CacheKey: {}", cacheKey);
            // Cache GET
            authFactResponse = AuthFactResponseLFUCacheProxy.get(cacheKey);
        }else {
            authFactResponse = supplementUserTracingInner(authFactRequest);
            // Cache PUT
            // AuthFactResponseLFUCache.add(cacheKey, authFactResponse);
            AuthFactResponseLFUCacheProxy.add(cacheKey, authFactResponse);
        }
        return authFactResponse;
    }

    public AuthFactResponse supplementUserTracingInner(AuthFactRequest authFactRequest) {
        // 看这个查询结果下面没用，不知道这行干啥用的，注释掉
        // List<UserPermission> userPermissions = AuthQueryServiceProxy.queryPermissions("123", "db", "table", "select");
        XSUtil.debug("InParam authFactRequest: {}", JsonUtil.convertObjectToString(authFactRequest));
        AuthFactResponse authFactResponse = new AuthFactResponse();
        try {
            DB db = authFactRequest.getDb();
            parseSQLAndSetValues(db, authFactResponse);
            supplementTableAuthInfo(authFactRequest.getUserId(), db, authFactResponse);
            supplementDesensitizeInfo(db, authFactResponse);
            authFactResponse.setCode(ErrorCode.RESULT_1001.getCode());
            authFactResponse.setMessage(ErrorCode.RESULT_1001.getMessage());
            XSUtil.debug("OutParam authFactResponse: {}", JsonUtil.convertObjectToString(authFactResponse));
        } catch (Throwable e) {
            authFactResponse.setCode(ErrorCode.RESULT_4009.getCode());
            authFactResponse.setMessage(StrUtil.format("{}（Local）。({})", ErrorCode.RESULT_4009.getMessage(), e.getMessage()));
            XSUtil.debug("supplementUserTracing ERROR: {}", e.getMessage());
            StartConfig.throwableStackPrinter(e);
        }
        return authFactResponse;
    }

    private void parseSQLAndSetValues(DB db, AuthFactResponse authFactResponse) {
        try {
            ResultDTO sqlResultDTO = null;
            String cacheKey = String.valueOf(HashUtil.apHash(db.getSql()));
            if (SqlParserLFUCacheProxy.hasKey(cacheKey)) {
                sqlResultDTO = (ResultDTO) SqlParserLFUCacheProxy.get(cacheKey);
            } else {
                SqlParser sqlParser = new TableAndColumnFinder();
                sqlResultDTO = sqlParser.find(db.getSql());
                SqlParserLFUCacheProxy.add(cacheKey, sqlResultDTO);
            }
            authFactResponse.setOperate(sqlResultDTO.getSqlType());
            List<Table> tableList = sqlResultDTO.getTableFinderDTOS().stream().map(tableFinderDTO -> {
                Table table = Table.builder().tabName(tableFinderDTO.getTable())
                        .allColumn(tableFinderDTO.getAllColumn()).columns(tableFinderDTO.getColumns().stream().map(colName ->
                                Column.builder().colName(colName).colAuthReason(XSConstants.AuthReason.NONE).build()).collect(Collectors.toList()))
                        .build();
                return table;
            }).collect(Collectors.toList());
            authFactResponse.setTables(tableList);
        } catch (Throwable e) {
            XSUtil.debug("SQL Parse ERROR: {} \n SQL: {}", e.getMessage(), db.getSql());
            StartConfig.throwableStackPrinter(e);
        }
    }

    private void supplementTableAuthInfo(String userId, DB db, AuthFactResponse authFactResponse) {
        if(authFactResponse == null || authFactResponse.getTables() == null) {
            System.out.println("supplementTableAuthInfo authFactResponse or getTables() is NULL");
            return;
        }
        authFactResponse.getTables().forEach(table -> {
            // List<UserPermission> upList = AuthQueryService.getInstance().queryPermissions(userId, db.getSchema(), table.getTabName(), authFactResponse.getOperate());
            List<UserPermission> upList = AuthQueryServiceProxy.queryPermissions(userId, db.getSchema(), table.getTabName(), authFactResponse.getOperate());
            System.out.println(Thread.currentThread().getId() + " DesenseDebug, XSSqlLocalSanitizer supplementTableAuthInfo upList-size: " + (upList==null?"NULL":upList.size()) + ", userId: " + userId + ", schema: " + db.getSchema() + ", tabName: " + table.getTabName() + ", operator: " + authFactResponse.getOperate());
            if (CollUtil.isEmpty(upList)) return;
            Optional<UserPermission> opt = upList.stream().findFirst();
            //supplement table auth
            if (opt.isPresent()) {
                table.setTabAuth(opt.get().getTabAuth());
                table.setTabAuthReason(opt.get().getTabAuthReason());
            }
            // 针对 SELECT * 语法 做一些特殊逻辑
            // - SELECT COLUMN 的情况：以SQL语法解析出的列字段为基础来做权限补全
            // - SELECT * 的情况：以本地表存储的权限配置列为基础来做权限补全
            if (table.getAllColumn()){
                List<Column> columns = upList.stream().map(up -> Column.builder()
                        .colName(up.getColName()).colAuth(up.getColAuth())
                        .colAuthReason(up.getColAuthReason()).build()).collect(Collectors.toList());
                table.setColumns(columns);
            }else {
                final Map<String, UserPermission> upMap = upList.stream().collect(Collectors.toMap(UserPermission::getColName, Function.identity()));
                table.getColumns().stream().forEach(column -> {
                    UserPermission userPermission = upMap.get(column.getColName());
                    if (Objects.nonNull(userPermission)){
                        column.setColAuth(userPermission.getColAuth());
                        column.setColAuthReason(userPermission.getColAuthReason());
                    }
                });
            }
        });

    }

    private void supplementDesensitizeInfo(DB db, AuthFactResponse authFactResponse) {
        if(authFactResponse == null || authFactResponse.getTables() == null) {
            System.out.println("XSSqlLocalSanitizer supplementDesensitizeInfo authFactResponse/getTables() is NULL");
            return;
        }
        String systemId =  StartConfig.getParamValueFromKey(StartConfig.systemId);
        // 压测优化1：集合迭代器改为并行流（集合数据量小，没啥性能提升空间）
        // 压测优化2：缓存穿透场景下的优化（穿透场景下的性能有明显提升[毫秒级降到微妙级]）AuthQueryService#queryDesensitizeInfo()
        authFactResponse.getTables().stream().forEach(table -> table.getColumns().stream().forEach(column -> {
            List<AuthDesenseAction> authDesensitizeActions = new ArrayList<>();
            //List<DesensitiseRule> desensitiseRuleList = AuthQueryService.getInstance().queryDesensitizeInfo(db.getSchema(), table.getTabName(), column.getColName());
            List<DesensitiseRule> desensitiseRuleList = AuthQueryServiceProxy.queryDesensitizeInfo(db.getSchema(), table.getTabName(), column.getColName());
            System.out.println(Thread.currentThread().getId() + " DesenseDebug, XSSqlLocalSanitizer supplementDesensitizeInfo desensitiseRuleList-size: " + (desensitiseRuleList==null?"NULL":desensitiseRuleList.size()) + ", schema: " + db.getSchema() + ", tabName: " + table.getTabName() + ", column: " + column.getColName());
            desensitiseRuleList.stream().forEach(rule ->{
                AuthDesenseAction authDesenseAction = AuthDesenseAction.builder()
                        .apiPath(rule.getApiPath())
                        .regular(rule.getRegular())
                        .systemId(systemId)
                        .patchPath(rule.getPatchPath())
                        .replaceSymbol(rule.getReplaceSymbol())
                        .isExecute(rule.getIsExecute()).build();
                authDesensitizeActions.add(authDesenseAction);
            });
            column.setAuthDesenseActions(authDesensitizeActions);
        }));
    }

    @Override
    public AuthFactResponse supplementUserTracingForJob(AuthFactRequest authFactRequest) {
        // NO NEED
        return null;
    }
}
